package hust.cs.javacourse.search.query.impl;

import hust.cs.javacourse.search.index.AbstractPosting;
import hust.cs.javacourse.search.index.AbstractPostingList;
import hust.cs.javacourse.search.index.AbstractTerm;
import hust.cs.javacourse.search.query.AbstractHit;
import hust.cs.javacourse.search.query.AbstractIndexSearcher;
import hust.cs.javacourse.search.query.Sort;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;

public class IndexSearcher extends AbstractIndexSearcher {
    /**
     * 缺省构造函数
     */
    public IndexSearcher(){}

    /**
     * 从指定索引文件打开索引，加载到index对象里. 一定要先打开索引，才能执行search方法
     * @param indexFile ：指定索引文件
     */
    public void open(String indexFile){
        try {
            index.load(new File(indexFile));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据单个检索词进行搜索
     * @param queryTerm ：检索词
     * @param sorter ：排序器
     * @return ：命中结果数组
     */
    public AbstractHit[] search(AbstractTerm queryTerm, Sort sorter){
        AbstractPostingList tempPostingList = index.search(queryTerm);
        if (tempPostingList==null) return null;
        List<AbstractHit> hits = new ArrayList<>();
        for (int i = 0; i < tempPostingList.size(); i++) {
            AbstractPosting tempPost = tempPostingList.get(i);
            Map<AbstractTerm, AbstractPosting> termToPosting = new HashMap<>();
            termToPosting.put(queryTerm, tempPost);
            AbstractHit hit = new Hit(tempPost.getDocId(), index.getDocName(tempPost.getDocId()), termToPosting);
            double score = sorter.score(hit);
            hit.setScore(score);
            hits.add(hit);
        }
        sorter.sort(hits);
        return hits.toArray(new AbstractHit[hits.size()]);
    }

    /**
     *
     * 根据二个检索词进行搜索
     * @param queryTerm1 ：第1个检索词
     * @param queryTerm2 ：第2个检索词
     * @param sorter ：    排序器
     * @param combine ：   多个检索词的逻辑组合方式
     * @return ：命中结果数组
     */
    //待修改
    public AbstractHit[] search(AbstractTerm queryTerm1, AbstractTerm queryTerm2, Sort sorter, LogicalCombination combine){
        List<AbstractHit> hits = new ArrayList<>();
        AbstractPostingList postingList1 = index.search(queryTerm1);
        AbstractPostingList postingList2 = index.search(queryTerm2);
        Map<AbstractTerm, AbstractPosting> map = new HashMap<>();

        //或OR逻辑搜索
        if (combine == LogicalCombination.OR) {
            if(postingList1==null || postingList2==null){
                if(postingList1==null) return this.search(queryTerm2, sorter);//如果queryTerm1不在任何一个文件夹中存在，直接搜索queryTerm2
                else return this.search(queryTerm1, sorter);//如果queryTerm2不在任何一个文件夹中存在，直接搜索queryTerm1
            }
            else{
                int i = 0, j = 0;

                for (; i < postingList1.size() && j < postingList2.size();) {
                    AbstractPosting post1 = postingList1.get(i);
                    AbstractPosting post2 = postingList2.get(j);
                    //当两个文档相同时，将两个检索词和对应文档都加入到命中文档中
                    if (post1.getDocId() == post2.getDocId()) {
                        map.put(queryTerm1, post1);
                        map.put(queryTerm2, post2);
                        AbstractHit hit = new Hit(post1.getDocId(), index.getDocName(post1.getDocId()), map);
                        double score = sorter.score(hit);
                        hit.setScore(score);
                        hits.add(hit);
                        i++;
                        j++;
                    }

                    //优先加入docId小的
                    else if (post1.getDocId() < post2.getDocId()) {
                        map.put(queryTerm1, post1);
                        AbstractHit hit = new Hit(post1.getDocId(), index.getDocName(post1.getDocId()), map);
                        double score = sorter.score(hit);
                        hit.setScore(score);
                        hits.add(hit);
                        i++;
                    }
                    else {
                        map.put(queryTerm2, post2);
                        AbstractHit hit = new Hit(post2.getDocId(), index.getDocName(post2.getDocId()), map);
                        double score = sorter.score(hit);
                        hit.setScore(score);
                        hits.add(hit);
                        j++;
                    }
                }

                //将剩下的文档加入
                for(;i < postingList1.size();i++) {
                    AbstractPosting post = postingList1.get(i);
                    map.put(queryTerm1, post);
                    AbstractHit hit = new Hit(post.getDocId(), index.getDocName(post.getDocId()), map);
                    double score = sorter.score(hit);
                    hit.setScore(score);
                    hits.add(hit);
                }
                for(;j < postingList2.size(); j++) {
                    AbstractPosting post = postingList2.get(j);
                    map.put(queryTerm2, post);
                    AbstractHit hit = new Hit(post.getDocId(), index.getDocName(post.getDocId()), map);
                    double score = sorter.score(hit);
                    hit.setScore(score);
                    hits.add(hit);
                }
            }
        }

        //与AND逻辑搜索，已改进
        else {
            if(postingList1==null || postingList2==null) return null;//如果两个词的某一个不在任何一个文档中存在，则返回null
            for(int i= 0, j = 0; i < postingList1.size() && j < postingList2.size();) {
                AbstractPosting post1 = postingList1.get(i);
                AbstractPosting post2 = postingList2.get(j);
                //当两个文档相同时，将两个检索词和对应文档都加入到命中文档中
                if (post1.getDocId() == post2.getDocId()) {
                    map.put(queryTerm1, post1);
                    map.put(queryTerm2, post2);
                    AbstractHit hit = new Hit(post1.getDocId(), index.getDocName(post1.getDocId()), map);
                    double score = sorter.score(hit);
                    hit.setScore(score);
                    hits.add(hit);
                    i++;
                    j++;
                }
                else if (post1.getDocId() < post2.getDocId()) i++;
                else j++;
            }
        }

        sorter.sort(hits);
        return hits.toArray(new AbstractHit[hits.size()]);
    }
}
